package cloud.topdaddy.admin.system.controller;

import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import cloud.topdaddy.admin.system.entity.SysMenu;
import cloud.topdaddy.admin.system.entity.SysRoleMenu;
import cloud.topdaddy.admin.system.entity.SysUserRole;
import cloud.topdaddy.admin.system.service.SysMenuService;
import cloud.topdaddy.admin.system.service.SysRoleMenuService;
import cloud.topdaddy.admin.system.service.SysUserRoleService;
import cloud.topdaddy.base.common.resp.R;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Caching;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.*;

@RestController
@RequestMapping("/sysMenu")
public class SysMenuController {

    @Resource
    private SysMenuService sysMenuService;
    @Resource
    private SysUserRoleService sysUserRoleService;
    @Resource
    private SysRoleMenuService sysRoleMenuService;

    // 基础 CRUD

    @GetMapping("/list")
    @SaCheckLogin
    @SaCheckPermission("system:menu:list")
    public R<List<SysMenu>> list() {
        return R.ok(sysMenuService.lambdaQuery()
                .eq(SysMenu::getDeletedFlag, false)
                .list());
    }

    @GetMapping("/{menuId}")
    @SaCheckLogin
    public R<SysMenu> get(@PathVariable("menuId") Long menuId) {
        return R.ok(sysMenuService.getById(menuId));
    }

    @PostMapping("/add")
    @SaCheckLogin
    @SaCheckPermission("system:menu:add")
    @Caching(evict = {
        @CacheEvict(cacheNames = "auth:perms#10m", allEntries = true),
        @CacheEvict(cacheNames = "auth:myMenus#5m", allEntries = true),
        @CacheEvict(cacheNames = "auth:myPerms#5m", allEntries = true)
    })
    public R<String> add(@RequestBody @Valid SysMenu menu) {
        menu.setCreateTime(LocalDateTime.now());
        sysMenuService.save(menu);
        return R.ok("新增成功");
    }

    @PutMapping("/update")
    @SaCheckLogin
    @SaCheckPermission("system:menu:update")
    @Caching(evict = {
        @CacheEvict(cacheNames = "auth:perms#10m", allEntries = true),
        @CacheEvict(cacheNames = "auth:myMenus#5m", allEntries = true),
        @CacheEvict(cacheNames = "auth:myPerms#5m", allEntries = true)
    })
    public R<String> update(@RequestBody @Valid SysMenu menu) {
        menu.setUpdateTime(LocalDateTime.now());
        sysMenuService.updateById(menu);
        return R.ok("更新成功");
    }

    @DeleteMapping("/delete")
    @SaCheckLogin
    @SaCheckPermission("system:menu:delete")
    @Caching(evict = {
        @CacheEvict(cacheNames = "auth:perms#10m", allEntries = true),
        @CacheEvict(cacheNames = "auth:myMenus#5m", allEntries = true),
        @CacheEvict(cacheNames = "auth:myPerms#5m", allEntries = true)
    })
    public R<String> delete(@RequestParam("menuIdList") List<Long> menuIdList) {
        if (menuIdList != null && !menuIdList.isEmpty()) {
            sysMenuService.removeBatchByIds(menuIdList);
            // 清理关系
            sysRoleMenuService.lambdaUpdate().in(SysRoleMenu::getMenuId, menuIdList).remove();
        }
        return R.ok("删除成功");
    }

    // 当前登录用户的菜单树
    @GetMapping("/my/tree")
    @SaCheckLogin
    @Cacheable(cacheNames = "auth:myMenus#5m", key = "T(java.lang.String).valueOf(T(cn.dev33.satoken.stp.StpUtil).getLoginIdAsLong())")
    public R<List<Map<String, Object>>> myMenuTree() {
        Long uid = StpUtil.getLoginIdAsLong();
        List<Long> roleIds = sysUserRoleService.lambdaQuery()
                .eq(SysUserRole::getUserId, uid)
                .list().stream().map(SysUserRole::getRoleId).toList();
        if (roleIds.isEmpty()) {
            return R.ok(Collections.emptyList());
        }
        List<Long> menuIds = sysRoleMenuService.lambdaQuery()
                .in(SysRoleMenu::getRoleId, roleIds)
                .list().stream().map(SysRoleMenu::getMenuId).distinct().toList();
        if (menuIds.isEmpty()) {
            return R.ok(Collections.emptyList());
        }
        List<SysMenu> menus = sysMenuService.lambdaQuery()
                .in(SysMenu::getMenuId, menuIds)
                .eq(SysMenu::getDeletedFlag, false)
                .eq(SysMenu::getDisabledFlag, false)
                .list();
        List<Map<String, Object>> tree = buildTree(menus);
        return R.ok(tree);
    }

    // 当前登录用户的权限点（apiPerms 和 webPerms）
    @GetMapping("/my/perms")
    @SaCheckLogin
    @Cacheable(cacheNames = "auth:myPerms#5m", key = "T(java.lang.String).valueOf(T(cn.dev33.satoken.stp.StpUtil).getLoginIdAsLong())")
    public R<Map<String, List<String>>> myPerms() {
        Long uid = StpUtil.getLoginIdAsLong();
        List<Long> roleIds = sysUserRoleService.lambdaQuery()
                .eq(SysUserRole::getUserId, uid)
                .list().stream().map(SysUserRole::getRoleId).toList();
        if (roleIds.isEmpty()) {
            return R.ok(Map.of("apiPerms", List.of(), "webPerms", List.of()));
        }
        List<Long> menuIds = sysRoleMenuService.lambdaQuery()
                .in(SysRoleMenu::getRoleId, roleIds)
                .list().stream().map(SysRoleMenu::getMenuId).distinct().toList();
        if (menuIds.isEmpty()) {
            return R.ok(Map.of("apiPerms", List.of(), "webPerms", List.of()));
        }
        List<SysMenu> menus = sysMenuService.lambdaQuery()
                .in(SysMenu::getMenuId, menuIds)
                .eq(SysMenu::getDeletedFlag, false)
                .eq(SysMenu::getDisabledFlag, false)
                .list();

        List<String> apiPerms = menus.stream()
                .map(SysMenu::getApiPerms)
                .filter(StringUtils::isNotBlank)
                .distinct()
                .toList();
        List<String> webPerms = menus.stream()
                .map(SysMenu::getWebPerms)
                .filter(StringUtils::isNotBlank)
                .distinct()
                .toList();
        return R.ok(Map.of("apiPerms", apiPerms, "webPerms", webPerms));
    }

    private List<Map<String, Object>> buildTree(List<SysMenu> menus) {
        Map<Long, Map<String, Object>> idNodeMap = new HashMap<>();
        for (SysMenu m : menus) {
            Map<String, Object> node = new LinkedHashMap<>();
            node.put("menuId", m.getMenuId());
            node.put("menuName", m.getMenuName());
            node.put("menuType", m.getMenuType());
            node.put("parentId", m.getParentId());
            node.put("sort", m.getSort());
            node.put("path", m.getPath());
            node.put("component", m.getComponent());
            node.put("frameFlag", m.getFrameFlag());
            node.put("frameUrl", m.getFrameUrl());
            node.put("cacheFlag", m.getCacheFlag());
            node.put("visibleFlag", m.getVisibleFlag());
            node.put("disabledFlag", m.getDisabledFlag());
            node.put("apiPerms", m.getApiPerms());
            node.put("permsType", m.getPermsType());
            node.put("webPerms", m.getWebPerms());
            node.put("icon", m.getIcon());
            node.put("contextMenuId", m.getContextMenuId());
            node.put("children", new ArrayList<Map<String, Object>>());
            idNodeMap.put(m.getMenuId(), node);
        }
        List<Map<String, Object>> roots = new ArrayList<>();
        for (SysMenu m : menus) {
            Map<String, Object> node = idNodeMap.get(m.getMenuId());
            Long pid = m.getParentId();
            if (pid == null || !idNodeMap.containsKey(pid) || Objects.equals(pid, 0L)) {
                roots.add(node);
            } else {
                List<Map<String, Object>> children = (List<Map<String, Object>>) idNodeMap.get(pid).get("children");
                children.add(node);
            }
        }
        // 按 sort 排序
        Comparator<Map<String, Object>> cmp = Comparator.comparing(o -> Optional.ofNullable((Integer) o.get("sort")).orElse(0));
        sortTree(roots, cmp);
        return roots;
    }

    @SuppressWarnings("unchecked")
    private void sortTree(List<Map<String, Object>> nodes, Comparator<Map<String, Object>> cmp) {
        nodes.sort(cmp);
        for (Map<String, Object> n : nodes) {
            List<Map<String, Object>> children = (List<Map<String, Object>>) n.get("children");
            if (children != null && !children.isEmpty()) {
                sortTree(children, cmp);
            }
        }
    }
}